<?xml version="1.0" encoding="UTF-8"?>
<!--
  Converts an iTunes library XML file into XSPF playlists and an
  XSPF-compliant, extended hierarchical playlist format.

  Released under the terms of the MIT license 2006 by Darren Kulp
-->
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns="http://xspf.org/ns/0/"
  xmlns:sift="http://kulp.ch/sift/2006/08" version="2.0">
  <xsl:output method="xml" indent="yes" name="xml"/>
  <!-- PARAMETERS -->
  <xsl:param name="mode" select="'all'"/><!-- or single or index -->
  <xsl:param name="odir" select="'.'"/><!-- output directory -->
  <xsl:param name="psep" select="'/'"/><!-- path element separator -->
  <xsl:param name="indexFile" select="'index.xspf'"/>
  <xsl:param name="playlistPersistentId"/>
  <!-- This list is not complete, and is currently based largely on my personal needs for another project -->
  <xsl:param name="includeSongAttributes" select="'Bit Rate,Compilation,Composer,Date Added,Disabled,Disc Count,Disc Number,File Creator,File Type,Genre,Persistent ID,Play Count,Play Date UTC,Rating,Sample Rate,Size,Track Count,Track Number,Track Type,Year'"/>
  <!-- GLOBALS -->
  <xsl:variable name="specials" select="tokenize('Master,All Items,Party Shuffle,Videos,Folder',',')"/>
  <xsl:variable name="songAttrs" select="tokenize($includeSongAttributes,',')"/>
  <xsl:variable name="tracks" select="sift:dictLookup(/plist/dict,'Tracks','dict')"/>

  <xsl:template match="/">
    <xsl:if test="$mode = 'all'">
      <xsl:for-each select="sift:dictLookup(/plist/dict,'Playlists','array')/dict">
        <xsl:call-template name="writePlaylist"/>
      </xsl:for-each>
    </xsl:if>
    <xsl:if test="$mode = 'index' or $mode = 'all'">
      <xsl:call-template name="writeIndex"/>
    </xsl:if>
    <xsl:if test="$mode = 'single'">
      <xsl:call-template name="writePlaylist">
        <xsl:with-param name="node" select="sift:playlistById(sift:dictLookup(/plist/dict,'Playlists','array'),$playlistPersistentId)"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <!--*************************   NAMED TEMPLATES   *************************-->
  <xsl:template name="writeIndex">
    <xsl:result-document href="{concat($odir,$psep,$indexFile)}" format="xml">
      <playlist root="true">
        <xsl:call-template name="listPlaylists"/>
      </playlist>
    </xsl:result-document>
  </xsl:template>

  <xsl:template name="writePlaylist">
    <xsl:param name="node" select="."/>
    <xsl:result-document href="{concat($odir,$psep,sift:dictLookup($node,'Playlist Persistent ID'),'.xspf')}" format="xml">
      <xsl:call-template name="playlist">
        <xsl:with-param name="includeSongs" select="1"/>
        <xsl:with-param name="node" select="$node"/>
      </xsl:call-template>
    </xsl:result-document>
  </xsl:template>

  <xsl:template name="listPlaylists">
    <xsl:param name="node" select="sift:dictLookup(/plist/dict,'Playlists','array')"/>
    <xsl:for-each select="$node/dict">
      <xsl:call-template name="playlist"/>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name="playlist">
    <xsl:param name="node" select="."/>
    <xsl:param name="includeSongs" select="0"/>
    <xsl:param name="includeChildren" select="1"/>
    <xsl:param name="inhibitIfChild" select="$node/key[. = 'Parent Persistent ID']"/>
    <xsl:choose>
      <xsl:when test="$inhibitIfChild and not($includeSongs)"/>
      <xsl:otherwise>
        <xsl:variable name="playlistPersistentId" select="sift:dictLookup($node,'Playlist Persistent ID')"/>
        <xsl:variable name="playlistId" select="sift:dictLookup($node,'Playlist ID','integer')"/>
        <playlist>
          <title><xsl:value-of select="sift:dictLookup($node,'Name')"/></title>
          <identifier><xsl:text>itunes:playlist:</xsl:text><xsl:value-of select="$playlistPersistentId"/></identifier>
          <extension application="http://kulp.ch/sift/"><!-- TODO: Explain -->
            <sift:playlistId><xsl:value-of select="$playlistId"/></sift:playlistId>
            <xsl:if test="sift:dictLookup($node,'Smart Info','data')">
              <sift:attribute name="Playlist Type">Smart</sift:attribute>
            </xsl:if>
            <xsl:for-each select="$specials">
              <xsl:if test="sift:dictLookup($node,string(.),'true')">
                <sift:attribute name="Special Type"><xsl:value-of select="."/></sift:attribute>
              </xsl:if>
            </xsl:for-each>
          </extension>
          <trackList>
            <xsl:if test="$includeSongs">
              <xsl:for-each select="sift:dictLookup($node,'Playlist Items','array')/dict">
                <xsl:call-template name="track"/>
              </xsl:for-each>
            </xsl:if>
          </trackList>
          <xsl:variable name="children" select="sift:childrenById($node/..,$playlistPersistentId)"/>
          <xsl:if test="$includeChildren and $children">
            <extension application="http://kulp.ch/sift/ph.xml"><!-- TODO: Explain -->
              <sift:children>
                <xsl:for-each select="$children">
                  <xsl:call-template name="playlist">
                    <xsl:with-param name="inhibitIfChild" select="0"/>
                    <xsl:with-param name="includeChildren" select="1"/>
                  </xsl:call-template>
                </xsl:for-each>
              </sift:children>
            </extension>
          </xsl:if>
        </playlist>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="track">
    <xsl:param name="node" select="."/>
    <!-- Assuming Track ID is first for efficiency gains -->
    <xsl:variable name="track" select="$tracks/dict[integer[1] = $node/integer[1]]"/>
    <track>
      <location><xsl:value-of select="sift:dictLookup($track,'Location')"/></location>
      <creator><xsl:value-of select="sift:dictLookup($track,'Artist')"/></creator>
      <album><xsl:value-of select="sift:dictLookup($track,'Album')"/></album>
      <title><xsl:value-of select="sift:dictLookup($track,'Name')"/></title>
      <duration><xsl:value-of select="sift:dictLookup($track,'Total Time','integer')"/></duration>
      <extension application="http://kulp.ch/sift/">
        <xsl:for-each select="$songAttrs">
          <xsl:variable name="content" select="sift:dictLookup($track,.)"/>
          <xsl:if test="string-length($content) > 0">
            <sift:attribute name="{.}"><xsl:value-of select="$content"/></sift:attribute>
          </xsl:if>
        </xsl:for-each>
      </extension>
    </track>
  </xsl:template>

  <!--****************************   FUNCTIONS   ****************************-->
  <xsl:function name="sift:dictLookup">
    <xsl:param name="node"/>
    <xsl:param name="key"/>
    <xsl:param name="dataType"/>
    <xsl:variable name="temp" select="$node/*[name() != 'key'][preceding-sibling::key[1] = $key]"/>
    <xsl:choose>
      <xsl:when test="not($dataType)"><xsl:copy-of select="$temp"/></xsl:when>
      <xsl:otherwise><xsl:copy-of select="$temp[name() = $dataType]"/></xsl:otherwise>
    </xsl:choose>
    <!-- TODO: Support empty elements like <true /> and <false /> -->
  </xsl:function>

  <xsl:function name="sift:dictLookup">
    <xsl:param name="node"/>
    <xsl:param name="key"/>
    <xsl:value-of select="sift:dictLookup($node,$key,'')"/>
  </xsl:function>

  <xsl:function name="sift:playlistById">
    <xsl:param name="node"/>
    <xsl:param name="id"/>
    <xsl:copy-of select="$node/dict[child::string[preceding-sibling::key[1] = 'Playlist Persistent ID'] = $id]"/>
  </xsl:function>

  <xsl:function name="sift:childrenById">
    <xsl:param name="node"/>
    <xsl:param name="id"/>
    <xsl:copy-of select="$node/dict[child::string[preceding-sibling::key[1] = 'Parent Persistent ID'] = $id]"/>
  </xsl:function>
</xsl:stylesheet>
<!-- vi: set ts=2 sw=2 et: -->
